home *** CD-ROM | disk | FTP | other *** search
/ Delphi Developer's Kit 1996 / Delphi Developer's Kit 1996.iso / power / dbcvt / dbcvt.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1995-12-22  |  13.7 KB  |  505 lines

  1. unit DBcvt;
  2. (*
  3.     Unit to import an ascii delmited file in to a (paradox) table
  4.     1)    This file was formated with tabs set to 2.
  5.     2)    On a 486/33 this parses about 200 linse per second (with 10 fields per line.)
  6.             It's not fast but it works.
  7.  
  8.     Author: William R. Florac
  9.   Company: FITCO, Verona, WI (wee little company from my house)
  10.     Copyright 1995, FITCO.  All rights reserved.
  11.  
  12.  1)  Users of DBCVT (and it's components) must accept this disclaimer of
  13.      warranty: "DBCVT is supplied as is.  The author disclaims all
  14.      warranties, expressed or implied, including, without limitation,
  15.      the warranties of merchantability and of fitness for any purpose.
  16.      The author assumes no liability for damages, direct or conse-
  17.      quential, which may result from the use of DBCVT."
  18.  
  19.     2) This component is donated to the public as public domain.
  20.  
  21.     3) This component can be freely used and distributed in commercial and private
  22.      environments provided this notice is not modified in any way.
  23.  
  24.   4) If you do find this component handy and you feel guilty
  25.     for using such a great product without paying someone,
  26.     please feel free to send a few bucks ($25) to support further
  27.     development.
  28.  
  29.  5) This file was formated with tabs set to 2.
  30.  
  31.     Please forward any comments or suggestions to Bill Florac at:
  32.          email: flash@etcconnect.com
  33.         mail: FITCO
  34.                     209 Jenna Dr
  35.                     Verona, WI  53593
  36.  
  37.     Revision History
  38.     1.0     9-15-95    Initial release.
  39. *)
  40.  
  41. interface
  42.  
  43. uses
  44.   SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  45.   Forms, Dialogs, DBtables, DB, StdCtrls;
  46.  
  47. type
  48.   TAscii2DB = class(TComponent)
  49.   private
  50.     { Private declarations }
  51.         Fdelimiter: char;
  52.         Fseparator: char;
  53.         EscBS: boolean;
  54.     FDestination: TTable;
  55.     FAsciiFile: TFileName;
  56.         FTickCount: word;
  57.         FFieldList: tstrings;
  58.         FOnTick: TNotifyEvent;
  59.         Fcount: longInt;
  60.         ProgressForm: TForm;
  61.         ProgressCancel: TButton;
  62.         Busy: boolean;
  63.     FShowDlg: boolean;
  64.         procedure SetFieldList(value: TStrings);
  65.         procedure SetCount(value: LongInt);
  66.         procedure AbortTransfer(Sender: TObject);
  67.   protected
  68.     { Protected declarations }
  69.         function Parseline(line: string; var list: tstringlist): boolean;
  70.         procedure DoUpdate;
  71.   public
  72.     { Public declarations }
  73.         ErrorCode: integer;
  74.         ParsedCount: longInt;
  75.         constructor Create(Aowner: Tcomponent); override;
  76.         destructor Destroy; override;
  77.         procedure Execute;
  78.         function GetErrorString(Ecode: integer): string;
  79.         function GetRecordCount:LongInt;
  80.         procedure StopExecute;
  81.   published
  82.     { Published declarations }
  83.         property Delimiter: char read Fdelimiter write Fdelimiter default '"';
  84.         property Separator: char read Fseparator write Fseparator default ',';
  85.         property RemoveESC: boolean read EscBS write EscBS default true;
  86.     property Destination: TTable read FDestination write FDestination;
  87.     property AsciiFile: TFileName read FAsciiFile write FAsciiFile;
  88.         property TickCount: word read FTickCount write FTickCount default 100;
  89.         property RecordLimit: LongInt read FCount write SetCount default 0;
  90.         property FieldList: TStrings read FFieldList write SetFieldList;
  91.         property OnTick: TnotifyEvent read FOnTick write FOnTick;
  92.         property ShowDlg: boolean read FshowDlg write FShowDlg default false;
  93.   end;
  94.  
  95. procedure Register;
  96.  
  97. implementation
  98.  
  99. constructor TAscii2DB.Create(Aowner: Tcomponent);
  100. begin
  101.     inherited create(Aowner);
  102.     Busy := false;
  103.     Fdelimiter := '"';
  104.     Fseparator := ',';
  105.   FShowDlg := False;
  106.     EscBS := true;
  107.     FTickCount := 100;
  108.     FCount := 0;
  109.     FFieldList := Tstringlist.create;
  110.     ProgressForm := TForm.Create(AOwner);
  111.   FShowDlg := False;
  112.  
  113.     with ProgressForm do begin
  114.         Parent := Parent;
  115.         ClientWidth := 200;
  116.         ClientHeight:= 40;
  117.         BorderStyle := bsSingle;
  118.         BorderIcons := [];
  119.         Caption := 'Transfer Progress';
  120.         FormStyle := fsStayOnTop;
  121.         end;
  122.     ProgressCancel := Tbutton.Create(AOwner);
  123.     with ProgressCancel do begin
  124.         Parent := ProgressForm;
  125.         left := 0;
  126.         width := ProgressForm.ClientWidth;
  127.         top := ProgressForm.ClientHeight div 2 ;
  128.         height := top;
  129.         caption :='&Cancel';
  130.         OnClick := AbortTransfer;
  131.         end;
  132.     end; {of create}
  133.  
  134. destructor TAscii2DB.Destroy;
  135. begin
  136.     FFieldList.Free;
  137.     inherited destroy;
  138.     end; {of destroy}
  139.  
  140. procedure Tascii2DB.Execute;
  141. const
  142.     RecordCount: LongInt = 0;
  143.  
  144. var
  145.     ParsedList: tstringlist;
  146.     InFile: TextFile;
  147.     Line: String;
  148.     DBIndex: integer;
  149.     AsciiIndex: integer;
  150.     FieldIndex: array[0..50] of integer;
  151.     x: integer;
  152.   NewRect: TRect;
  153.     Percent: Integer;
  154.     EndOfIt: LongInt;
  155.     PText: String;
  156.  
  157. begin
  158.     if busy then begin
  159.         ErrorCode := 97;
  160.         exit;
  161.         end;
  162.     Busy := true;
  163.     {assume good}
  164.     ErrorCode := 0;
  165.     ParsedCount := 0;
  166.     {make sure table is working}
  167.     if FDestination <> nil then begin
  168.         {table must be inactive}
  169.         if FDestination.Active then FDestination.Close;
  170.         {can I get exclusive rights to table?}
  171.         try
  172.             FDestination.Exclusive := True;
  173.         except
  174.             on EDatabaseError do begin
  175.                 errorCode := 1;
  176.                 busy := false;
  177.                 exit;
  178.                 end;
  179.           end;
  180.  
  181.         {does source file exist?}
  182.         if not FileExists(AsciiFile) then begin
  183.             errorCode := 2;
  184.             busy := false;
  185.             exit;
  186.             end;
  187.  
  188.         if FShowDlg then RecordCount := GetRecordCount;
  189.         {open database}
  190.         try
  191.             FDestination.Open;
  192.         except
  193.             on EDatabaseError do begin
  194.                 errorCode := 3;
  195.                 busy := false;
  196.                 exit;
  197.                 end;
  198.          end;
  199.  
  200.         {does fieldlist count match database?}
  201.         if FDestination.FieldCount <> FFieldList.count then begin
  202.             errorCode := 6;
  203.             FDestination.Close; {close the database}
  204.             busy := false;
  205.             exit;
  206.             end;
  207.  
  208.         {can we find all the field names, save index to them}
  209.         for DBIndex := 0 to FFieldList.Count - 1 do begin
  210.             if UpperCase(FFieldList[DBIndex]) <> 'SKIP' then begin
  211.                 FieldIndex[DBIndex] := Fdestination.FieldDefs.IndexOf(FFieldList[DBIndex]);
  212.                 if FieldIndex[DBIndex] < 0 then begin    {exit if we did not find it}
  213.                     errorCode := 8;
  214.                     FDestination.Close; {close the database}
  215.                     busy := false;
  216.                     exit;
  217.                     end;
  218.                 end
  219.         else begin
  220.           FieldIndex[DBIndex] := 0;
  221.                 end;
  222.             end;
  223.         FDestination.EmptyTable;                 {empty old data}
  224.  
  225.         AssignFile(InFile, AsciiFile);  {open ascii file}
  226.         Reset(InFile);
  227.         ParsedList := TStringList.create; {create the string list}
  228.  
  229.         if FShowDlg then begin
  230.             ProgressForm.Left := TForm(Owner).Left + (TForm(Owner).Width div 2) - ProgressForm.Width div 2;
  231.             ProgressForm.Top := TForm(Owner).Top + (TForm(Owner).Height div 2) - ProgressForm.Height div 2;
  232.             ProgressForm.Show;
  233.             end;
  234.  
  235.         while (not eof(InFile)) and (errorCode = 0)do begin
  236.           readln(InFile,Line);
  237.             if not ParseLine(Line, ParsedList) then begin
  238.                 errorCode := 4;
  239.                 break;
  240.                 end
  241.             else begin
  242.                 if ParsedList.count <> FFieldList.Count then begin
  243.                     errorCode := 5;
  244.                     break;
  245.                     end;
  246.                 {This is part that is slow!}
  247.                 FDestination.Edit;
  248.                 FDestination.Insert;
  249.                 for DBIndex := 0 to FFieldList.Count - 1 do begin
  250.                     if UpperCase(FFieldList[DBIndex]) <> 'SKIP' then begin
  251.                         try
  252.                            FDestination.Fields[FieldIndex[DBindex]].AsString := ParsedList[DBIndex];
  253.                except
  254.                             on EDataBaseError do begin
  255.                                 errorCode := 7;
  256.                                 break;
  257.                                 end;
  258.                 end;
  259.                         end;
  260.                end;
  261.                 {call user update and give some time to other apps}
  262.             inc(ParsedCount);
  263.                 if FTickCount > 0 then begin
  264.                     if ParsedCount mod FTickCount = 0 then begin
  265.                         if FShowDlg then begin
  266.                       if FCount > RecordCount
  267.                             then EndOfIt := RecordCount
  268.                             else begin
  269.                                 if Fcount =0
  270.                 then EndOfIt := RecordCount
  271.                                 else EndOfIt := Fcount;
  272.                                 end;
  273.  
  274.                             if EndOfIt <> 0
  275.                             then Percent := ProgressForm.ClientWidth * ParsedCount div EndOfIt
  276.                             else Percent := 0;
  277.  
  278.                             {draw left background}
  279.                           NewRect := Rect(0, 0, Percent, ProgressForm.ClientHeight div 2);
  280.                           ProgressForm.Canvas.Brush.Style := bsSolid;
  281.                           ProgressForm.Canvas.Brush.Color := clRed;
  282.                           ProgressForm.Canvas.FillRect(NewRect);
  283.  
  284.                             {draw right background}
  285.                           NewRect := Rect(Percent, 0, ProgressForm.ClientWidth,
  286.                                                   ProgressForm.ClientHeight div 2);
  287.                           ProgressForm.Canvas.Brush.Color := clBtnFace;
  288.                           ProgressForm.Canvas.FillRect(NewRect);
  289.  
  290.                             {draw text}
  291.                             if EndOfIt <> 0
  292.                             then Ptext := IntToStr(100 * ParsedCount div EndOfIt) + '%'
  293.                             else Ptext := '0%';
  294.                           ProgressForm.Canvas.Brush.Style := bsClear;
  295.                 ProgressForm.Canvas.TextOut(90,2,Ptext);
  296.                             end;
  297.                         DoUpdate; {call user function}
  298.                         end;
  299.               end;
  300.                 {are we done?}
  301.                 if (Fcount > 0) and (ParsedCount >= Fcount) then break;
  302.                 end;
  303.           end;
  304.         {ok, shut it all down}
  305.         FDestination.Post;
  306.         ProgressForm.Close;
  307.     ParsedList.Free;             {get rid of my list}
  308.         CloseFile(InFile);         {close source file}
  309.         FDestination.Close;     {close the database}
  310.         busy := false;
  311.         end
  312.     else begin {no destination}
  313.         errorCode := 99;
  314.         busy := false;
  315.         end;
  316.     end; {of execute}
  317.  
  318. procedure Tascii2DB.AbortTransfer(Sender: TObject);
  319. begin
  320.     ErrorCode := 98;
  321.     end; {of aborttranfer}
  322.  
  323. function Tascii2DB.GetErrorString(Ecode: integer): string;
  324. begin
  325.     case eCode of
  326.         0: result := 'No errors detected.';
  327.         1: result := 'Can not get exclusive access to database.';
  328.          2: result := 'Ascii file does not exist. [' + AsciiFile + ']';
  329.         3: result := 'Can not open database.';
  330.         4: result := 'Error in ascii file';
  331.         5: result := 'Ascii table does not match database.';
  332.         6: result := 'Field count does not match database.';
  333.         7: result := 'Data type mismatch in ascii file.';
  334.         8: result := 'Field names do not match database.';
  335.  
  336.         97: result := 'Busy.';
  337.         98: result := 'User aborted.';
  338.         99: result := 'Desitination table does not exist.';
  339.       else result := 'Unknown error.';
  340.         end;
  341.     end; {of get errorstring}
  342.  
  343. function Tascii2DB.GetRecordCount: LongInt;
  344. var
  345.     InFile: TextFile;
  346.     Line: String;
  347.     counter: Longint;
  348. begin
  349.     if not FileExists(AsciiFile) then begin
  350.         Result := -1;
  351.         exit;
  352.         end;
  353.     AssignFile(InFile, AsciiFile);  {open ascii file}
  354.     Reset(InFile);
  355.     counter := 0;
  356.     while not EOF(InFile) do begin
  357.         readln(InFile,Line);
  358.         inc(counter);
  359.         end;
  360.     CloseFile(InFile);         {close source file}
  361.     Result := counter;
  362.     end; {of getrecordcount}
  363.  
  364. procedure Tascii2DB.StopExecute;
  365. begin
  366.     {setting error code aborts}
  367.     ErrorCode := 98;
  368.     end;
  369.  
  370.  
  371. procedure Tascii2DB.SetFieldList(Value: TStrings);
  372. begin
  373.   FFieldList.Assign(Value);
  374.     end; {of setfieldlist}
  375.  
  376. procedure Tascii2DB.SetCount(value: LongInt);
  377. begin
  378.     if value < 0 then value := 0;
  379.   FCount := value;
  380.     end; {of setcount}
  381.  
  382.  
  383. procedure Tascii2DB.DoUpdate;
  384. begin
  385.     Application.ProcessMessages;
  386.     if assigned(FOnTick) then FOnTick(Self);
  387.     end; {of doupdate}
  388.  
  389.  
  390. function Tascii2DB.parseline(line: string; var list: tstringlist): boolean;
  391. var
  392.     x,SepCount: integer;
  393.     inquote: boolean;
  394.     subline: string;
  395.   maxcount: integer;
  396.     oops: boolean;
  397. begin
  398. {  *** parse line ***
  399.     - look for separator marker or ENDOFLINE
  400.       separator can not be inbetween DELIMITERS
  401.          (if it is, it is part of the string)
  402.     - pull out segment and delete from string
  403.   - for each segment,
  404.           see if first character = " if so, nuke it
  405.           see if last character = " if so, nuke it
  406.              look for \" if found convert it to " (if enabled)
  407.              add to stringlist
  408. }
  409.  
  410.     result := true;
  411.     {clear the list}
  412.     List.clear;
  413.     {abort if 0 lenght string}
  414.   if Length(line) < 1 then begin
  415.         result := false;
  416.         exit;
  417.         end;
  418.  
  419.     {remove escaped "}
  420.     if EscBS then
  421.         while pos('\"', line) > 0 do delete(line,pos('\"', line),1);
  422.  
  423.     {flag to indicate if we are in a string record}
  424.     inquote := false;
  425.     {count to next separator}
  426.     SepCount := 0;
  427.   {no quote mistakes}
  428.     oops := false;
  429.     {number of characters to examine}
  430.     maxcount := Length(Line);
  431.     for x:= 1 to maxcount do begin
  432.         inc(SepCount);
  433.         {keep status to as to if we are inside delimiter}
  434.         if line[SepCount] = Fdelimiter then begin
  435.             {toggle status}
  436.             {make sure its at end of record or next to a separator}
  437.             if inquote then begin
  438.                 if SepCount <= maxcount-1 then begin
  439.                     if Line[SepCount+1] = Fseparator
  440.                     then inquote := false;
  441.                     end
  442.         else inquote := false; {EOR}
  443.                 end
  444.             else
  445.                 {if it is not just after a separator then it was really the end of}
  446.                 {the last string (i.e. string contained "<text>",<text>"}
  447.                 if SepCount > 1 then begin
  448.                     if Line[SepCount-1] <> Fseparator then begin
  449.                         inquote := false;
  450.                         oops := true;
  451.             end;
  452.           end
  453.                 else inquote := true;
  454.             end;
  455.         {ignore between delimiters}
  456.         if not inquote then begin
  457.             if line[SepCount] = Fseparator then begin {EOR reached}
  458.                 {get it}
  459.                 subline := copy(line, 1, SepCount-1);
  460.                 {delete it from original}
  461.                 delete(line,1,SepCount);
  462.                 {remove pre and post delimiters}
  463.                 if length(subline) > 0 then
  464.                     if subline[1] = Fdelimiter then
  465.                         delete(subline,1,1);
  466.                 if subline[length(subline)] = Fdelimiter then
  467.                     dec(subline[0]);
  468.                 {add it to the list}
  469.                 if oops then begin
  470.                     if list.Count >= 1 then
  471.                         list[list.Count-1] := list[list.Count-1] + '",' + subline;
  472.                     oops := false;
  473.                     end
  474.                 else
  475.                     list.add(subline);
  476.                 SepCount := 0;
  477.                 end;
  478.         end;
  479.         end;
  480.  
  481.     {clean up any remaining data}
  482.     if length(line) > 0 then begin
  483.         subline := line;
  484.         {remove pre and post delimiters}
  485.         if subline[1] = Fdelimiter then
  486.             delete(subline,1,1);
  487.         if subline[length(subline)] = Fdelimiter then
  488.             dec(subline[0]);
  489.             {add it to the list}
  490.         if oops then begin
  491.             list[list.Count-1] := list[list.Count-1] + '",' + subline + '<>';
  492.             oops := false;
  493.             end
  494.         else
  495.             list.add(subline);
  496.     end;
  497. end; {of parseline}
  498.  
  499. procedure Register;
  500. begin
  501.   RegisterComponents('Fitco', [Tascii2DB]);
  502. end; {of register}
  503.  
  504. end. {of unit}
  505.